因為 elasticsearch 要打很多字,寫文苦手如我決定縮寫它,所以會用 ES 代稱。
auto-complete 除了自動補完之外,另外一個說法是『猜你想搜尋什麼』也就是從各種資訊中猜測使用者想要搜尋的內容;我們可以從幾個方向去『猜』使用者的想法,第一個是機率論,紀錄比較多的關鍵字,就代表越多人討論,也就有可能越多人想問;另外一個方向會是從使用者的『歷史搜尋』中猜測,曾經搜尋過的,可能會想再搜尋一次,或是與其相關的主題。
要實作出使用者的搜尋紀錄,需要有一個 db 或檔案紀錄使用者搜尋歷程,這部分目前並沒有在這系列的計畫中。因此『猜』的方向會是從現有紀錄中,找到最相關的資訊為主。
之前實作時的想法大概分成三個部分:
本篇先來重現一下第一個部分:補完沒有打完的單字(詞),假設是 cov。
想法是這樣的:
(1) 找出以這個沒有打完的單字為前綴(prefix)的 token,
(2) 根據某種規則排序這些 token 誰會在最前面。
上述的 (1) 蠻好解的,只要透過 prefix-query 篩選出包含以 cov 為前綴的 token 的文件就可以了:
GET /covid19_tweets/_search
{
  "query": {
    "prefix": {
      "tweet": {
        "value": "cov"
      }
    }
  }
}
上述的 (2) 就有點困難,尤其是『根據某種規則排序』這件事;當時的解決方式是透過 significant_text 這個 aggregation 來完成。
先簡單說明一下 aggregation 是什麼,前面描述了很多搜尋的方法,但當搜尋出來的結果,想要進行彙整時,就會透過 aggregation 來處理;例如某個 query 搜尋出了 200 篇文章,這 200 篇文章我想要依據日期進行計算出每一天有幾篇文章,就可以透過 aggregation 來進行。
ES 提供很多 aggregation function 可以使用,significant_text 就是其中一個,他所執行的內容就是針對搜尋出來的文章,依據某種方式找出『重要的 token』,aggregation query 內容如下:
GET /covid19_tweets/_search
{
  "query": {
    "prefix": {
      "tweet": {
        "value": "cov"
      }
    }
  },
  "aggs": {
    "find_the_whole_token": {
      "significant_text": {
        "field": "tweet",
        "include": "cov.+",
        "size": 2,
        "min_doc_count": 100
      }
    }
  },
  "fields": ["aggregation"],
   "_source": false 
}
翻譯一下 aggs 內的語法:find_the_whole_token 是一個的 aggregation 名稱,這個 aggregation 要使用 significant_text 這個 ES 提供的 aggregation function,將其做用在 tweet 欄位,透過 significant_text 找出來的 token 要:
a.  符合  `cov.+` 這個 pattern(regex) 
b. 至少在 100 筆資料(documents)中出現
並且請最多回傳兩個這樣的 token 給我;ES 給了以下的產出:
{
  "took": 371,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 10000,
      "relation": "gte"
    },
    "max_score": 1,
    "hits": [
      ...(略)...
    ]
  },
  "aggregations": {
    "find_the_whole_token": {
      "doc_count": 50000,
      "bg_count": 354600,
      "buckets": [
        {
          "key": "covid",
          "doc_count": 32250,
          "score": 1.64217,
          "bg_count": 64500
        },
        {
          "key": "covid19",
          "doc_count": 16263,
          "score": 0.82811196,
          "bg_count": 32526
        }
      ]
    }
  }
}
這樣的搜尋找出的兩個 token 是:covid 和 covid19 ,看起來結果蠻符合預期的,但其實有一些不太理想的地方:
significant_text 效率沒有很好,這個搜尋花了一些時間,在一個搜尋的狀況下還沒有什麼影響,但當要進行 search as you type 時,使用者每打一個字就要等待一小段時間,會顯得相當緩慢,當使用者人數變多時也會對 ES 服務有很大的負擔。include 這個篩選機制來達到目的,並不是當初 significant_text 設計的使用方式。接下來幾篇會先照著之前的實作設計走完,把遇到覺得不太理想的地方都記錄下來,最後再來看看有沒有可以優化的地方。